/*************************************************************************
	> File Name: tcpserver.cpp
	> Author: xuwenlong
	> Mail: myxuan475@126.com
	> Created Time: 2018年01月22日 星期一 15时27分39秒
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include "serial.h"
#include "htcomhex.h"
#include <unordered_map>
#include <sys/eventfd.h>
namespace downlink {

SerialConnect::SerialConnect(const dbms::DeviceChannel_t &dc)
	: DownlinkConnect(dc)
	, comFd_(-1)
	, serial_(dc.com())
{
}

SerialConnect::~SerialConnect() { Close(); }

const std::string SerialConnect::Name() const { return serial_.name(); }

bool SerialConnect::ReConnect()
{
	Close();

	if (!Open()) {
		return false;
	}
	ClearReadBuffer();
	ClearWriteBuffer();

	HTELINK_LOG_DEBUG("Serial %s, baudrate: %d, bitsize: %d, "
					  "parity: %d, stopbits: %d",
		serial_.name(), serial_.baudrate(), (int)serial_.databit(),
		(int)serial_.parity(), (int)serial_.stopbit());
	return true;
}

void SerialConnect::ClearWriteBuffer() { FlushBuffer(comFd_, 0); }

void SerialConnect::ClearReadBuffer() { FlushBuffer(comFd_, 1); }

bool SerialConnect::Send(const uint8_t cmd[], int32_t len)
{
	return PortSend(comFd_, (const char *)cmd, len);
}

int32_t SerialConnect::Recv(uint8_t cmd[], int32_t max, int32_t timeout)
{
	return PortRecv(comFd_, (char *)cmd, max);
	// return PortRecv(comFd_, (char *)(cmd), max);
#if 0
	int32_t recv = 0;
	int32_t ret = 0;

	if (timeout < 500)
		timeout = 500;
	int32_t byte = 0;
	while ((ret = Available(timeout)) > 0) {

		if ((recv = PortRecv(comFd_, (char *)(&cmd[byte]), ret)) > 0) {
			byte += recv;
			recv = 0;
			if (byte >= max || timeout <= 0) {
				HTELINK_LOG_DEBUG(
					"recv limit, %d,%s clear", errno, strerror(errno));
				break;
			}
		} else {
			if (errno == 0) {
				return byte > 0;
			} else if (errno == EINTR) {
				HTELINK_LOG_DEBUG(
					"recv failed, %d,%s break", errno, strerror(errno));
				break;
			} else if (errno == EAGAIN) {
				HTELINK_LOG_DEBUG(
					"recv again, %d,%s continue", errno, strerror(errno));
				continue;
			} else {
				HTELINK_LOG_ERR(
					"recv failed, %d,%s break", errno, strerror(errno));
				break;
			}
		}
		if (timeout <= 0)
			break;
	}
	return byte > 0;
#endif
}

template <class T>
T FindMap(const std::unordered_map<int32_t, T> &map, int32_t key, int32_t def,
	const std::string &name)
{
	if (map.find(key) == map.end()) {
		HTELINK_LOG_WARN("%s(%u) not support, set default %d", name, key, def);
		return map.at(def);
	}
	return map.at(key);
}

bool SerialConnect::Open()
{
	comFd_ = OpenPort(serial_.name().c_str());
	if (comFd_ < 0) {
		HTELINK_LOG_ERR("open %s failed, %s", serial_.name(), strerror(errno));
		return false;
	}
	portinfo_t portinfo = {
		'0',							 // print prompt after receiving
		serial_.baudrate(),				 // baudrate: 9600
		(char)('0' + serial_.databit()), // databit: 8
		'0',							 // debug: off
		'0',							 // echo: off
		'2',							 // flow control: none
		(char)('0' + serial_.parity()),
		// parity: none
		(char)('0' + serial_.stopbit()), // stopbit: 1
		0								 // reserved
	};
	PortSet(comFd_, &portinfo);
	return true;
}

void SerialConnect::Close()
{
	if (comFd_ > 0) {
		close(comFd_);
	}
	comFd_ = -1;
}

int32_t SerialConnect::Available(int &timeout)
{
	fd_set fd;
	int ret;

	FD_ZERO(&fd);
	FD_SET(comFd_, &fd);

	timeval tm;
	tm.tv_sec = timeout / 1000;
	tm.tv_usec = (timeout % 1000) * 1000;

	ret = select(comFd_ + 1, &fd, NULL, NULL, &tm);

	timeout = tm.tv_sec * 1000 + tm.tv_usec / 1000;

	if (ret < 0) {
		HTELINK_LOG_DEBUG("receive error:%d, %s", ret, strerror(errno));
		Close();
		return ret;
	} else if (ret == 0) {
		return ret;
	}

	if (FD_ISSET(comFd_, &fd)) {
		return ret;
	}
	return 0;
}

int32_t SerialConnect::Handle() { return comFd_; }
void SerialConnect::CloseConnect() { Close(); }
bool SerialConnect::IsAvailable()
{
	int timeout = 1000;
	return Available(timeout) > 0;
}
} // namespace downlink
